Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rfc(feature): SDK Lifecycle Hooks #34

Merged
merged 5 commits into from
Mar 27, 2023
Merged

Conversation

AbhiPrasad
Copy link
Member

@AbhiPrasad AbhiPrasad commented Nov 3, 2022

Rendered RFC

This PR proposes the introduction of lifecycle hooks in the SDK, improving extensibility and usability of SDKs at Sentry.

To test out this RFC, we implemented it in the JavaScript SDK with great success, so now we are looking to propose and implement it in the other SDKs.

Lifecyle hooks can be registered on a Sentry client, and allow for integrations/sdk users to have finely grained control over the event lifecycle in the SDK.

interface Client {
  // ...

  on(hookName: string, callback: (...args: unknown) => void) => void;

  emit(hookName: string, ...args: unknown[]) => void;

  // ...
}

JS SDK work: getsentry/sentry-javascript#7262

text/0034-sdk-lifecycle-hooks.md Show resolved Hide resolved
text/0034-sdk-lifecycle-hooks.md Show resolved Hide resolved
text/0034-sdk-lifecycle-hooks.md Show resolved Hide resolved
text/0034-sdk-lifecycle-hooks.md Outdated Show resolved Hide resolved
@AbhiPrasad AbhiPrasad marked this pull request as ready for review March 16, 2023 14:38
@AbhiPrasad
Copy link
Member Author

I simplified and re-wrote this proposal based on the experience we had adding and using hooks within the JavaScript SDK. It is now ready for another set of reviews (only took a billion months lol)

@AbhiPrasad AbhiPrasad self-assigned this Mar 16, 2023
Copy link
Member

@sl0thentr0py sl0thentr0py left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shippppit
should we think of common hooks across SDKs or is this completely left to SDK authors?

@AbhiPrasad
Copy link
Member Author

should we think of common hooks across SDKs or is this completely left to SDK authors?

We will define a set of initial ones in the develop docs, but the rest can be left to SDK authors to solve their platform specific problems.

Since for now hooks are meant to be used internally I don't think we worry about SDK user concerns yet, but maybe we can re-evaluate this in 2-3 months after more SDKs adopt it.

Copy link
Member

@cleptric cleptric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great example of finding a very concise and simple solution to a problem that might be super platform-specific, so being able to extend this on an SDK by SDK basis is 👌

:shipit:


type HookCallback = (...args: unknown[]): void;

class Client {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused, who implements this Client? What exactly the end-user interacts with?

Copy link
Member

@cleptric cleptric Mar 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for showing an interest in our RFC process! 🚀
This is meant to be the client implementation we have in each SDK, see https://develop.sentry.dev/sdk/unified-api/#client

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think a layer higher and across multiple languages and clients. Sorry for the confusion, and thank you so much for your response!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cleptric, I also need some clarification. Does this mean I need to implement a client when I want to add lifecycle hooks? Can you share the code a user must write when implementing a lifecycle hook?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The on and emit hooks will be defined on the client interface (much like flush and capture_event are at the moment).

Usually users (if writing custom clients) are inheriting from a base client implementation anyway, so they should get the hooks functionality out of the box.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification, @AbhiPrasad 😃 .

On mobile, hardly anybody implements a custom client. Most users interact with the static API. Implementing a custom client and binding this to the hub, then binding the hub to the static API, will not be the cleanest API on Mobile. Our users usually only call SentrySDK.start with the options, and that's it.

Comment on lines +121 to +125
`beforeEnvelope`:

```ts
on('beforeEnvelope', callback: (envelope: Envelope) => void) => void;
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a hook for beforeEnvelope? Can we make it internal at least? AFAIK this is not exposed for clients and the whole envelope impl. is marked as internal in Mobile SDKs at least.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can make some hooks internal. I will clarify this in the RFC.


SDKs are expected to have a common set of hooks, but can also have SDK specific hooks.

## Hooks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's about hooks for startSession and endSession?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not an exhaustive list, but I will add those.

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great RFC and big +1 on the "implementing it as needed per SDK" approach!


Hooks are meant to be mostly internal APIs for integration authors, but we can also expose them to SDK users if there is a use case for it.

As hook callbacks are not processed by the client, they can be async functions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should clarify the mutability of the data (events, hints, breadcrumbs, etc) passed to the callbacks. If we allow callbacks to be async, we need to be aware that mutations made to these objects might come in too late, especially for hooks around processing and sending events.

Then again, I feel like we shouldn't prohibit mutation, as outlined by the use cases listed in this RFC (also this practical example).

When we write the dev spec for hooks, we should add that implementers need to think about mutability and async characteristics of the hook(s) they're implementing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I will add notes around this.

@mitsuhiko
Copy link
Member

I would like to explicitly document the requirements for callbacks with regards to forward compatibility. In particular in Python for instance we should expect them to always catch extra keyword arguments so that additional data can be passed in the future.

@yordis
Copy link

yordis commented Mar 20, 2023

@mitsuhiko, your concern is why I pointed out that having a single argument object is much better, so you avoid breaking changes. You can extend API without requiring different function definitions (to some extent)

Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this @AbhiPrasad 👏 .

text/0034-sdk-lifecycle-hooks.md Show resolved Hide resolved

# Proposal

SDK hooks live on the client, and are **stateless**. They are called in the order they are registered. SDKs can opt-in to whatever hooks they use, and there can be hooks unique to an SDK.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbhiPrasad, why did we put the hooks on the client and not the options as our existing hooks beforeSend, beforeBreadcrumb, etc.?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not meant to be replacements for the top level hooks we have for beforeSend or beforeBreadcrumb for the time being, since the hook names are dynamic strings.

The hooks are on the client so they can be registered dynamically. This is basically the event emitter pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to put this info in the RFC or the develop docs, as this was unclear to me when reading the RFC.

I didn't write JavaScript for 6 years or so, but event emitter still rings a bell now. For statically typed languages, we might have to adapt them to the language. For Swift and Java, it would maybe be something like addObserver and notifyObserver, or maybe we just stick to the JavaScript syntax. Thanks for explaining.


To document and approve new hooks, we will create a new page in the develop docs that lists all the hooks, and what they are used for.

`startTransaction`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also add our existing hooks as beforeSend, beforeTransaction, beforeBreadcrumb, etc. to have one way to define all hooks, and we should keep the existing hooks for backward compatibility. Otherwise, users need to use two different APIs for a similar use case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense to me - can add some notes

text/0034-sdk-lifecycle-hooks.md Show resolved Hide resolved
Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the answer to my question #34 (comment) is yes, then LGTM. Great work @AbhiPrasad 👏


To test out this RFC, we implemented it in the [JavaScript SDK](https://github.com/getsentry/sentry-javascript/blob/7aa20d04a3d61f30600ed6367ca7151d183a8fc9/packages/types/src/client.ts#L153) with great success, so now we are looking to propose and implement it in the other SDKs.

Lifecyle hooks can be registered on a Sentry client, and allow for integrations/sdk users to have finely grained control over the event lifecycle in the SDK.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

integrations/sdk users

That confused me a bit. I thought this API also targets our users, but as you @AbhiPrasad, explained in the TSC, this API is mostly internal. This information is essential to me as it completely changes how I evaluate this RFC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a remnant from before, forgot to change this!


# Proposal

SDK hooks live on the client, and are **stateless**. They are called in the order they are registered. SDKs can opt-in to whatever hooks they use, and there can be hooks unique to an SDK.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to put this info in the RFC or the develop docs, as this was unclear to me when reading the RFC.

I didn't write JavaScript for 6 years or so, but event emitter still rings a bell now. For statically typed languages, we might have to adapt them to the language. For Swift and Java, it would maybe be something like addObserver and notifyObserver, or maybe we just stick to the JavaScript syntax. Thanks for explaining.

- Integrations want to add information to envelopes before they are sent to Sentry.
- Integrations want to run code on transaction/span finish (to add additional spans to the transaction, for example).
- Integrations want to mutate an error on `captureException`
- Integrations want to override propagation behaviour (extracing/injecting outgoing headers)
Copy link
Member

@philipphofmann philipphofmann Mar 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Mobile, take a screenshot or attach the view hierarchy when capturing an exception. Could we replace our custom logic of attachment processors with that, @AbhiPrasad? We call some code that returns attachments and then bundle it into an envelope when capturing exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! We could for sure replace that with this, will add your example to the list!

@AbhiPrasad AbhiPrasad changed the title SDK Lifecycle Hooks rfc(feature): SDK Lifecycle Hooks Mar 27, 2023
@AbhiPrasad AbhiPrasad merged commit e3d5692 into main Mar 27, 2023
@AbhiPrasad AbhiPrasad deleted the rfc-sdk-lifecycle-hooks branch March 27, 2023 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants